using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace PSSymbolStore
{
    public static class Helpers
    {
        internal static List<string> AllowedExtensions = new List<string>(new[] { ".dbg", ".exe", ".pdb" });

        static Helpers()
        {
            AllowedExtensions.Sort(StringComparer.OrdinalIgnoreCase);
        }

        private static readonly IntPtr pHandle = Process.GetCurrentProcess().Handle;

        public static SymbolFileInformation GetSymbolFileInformation(string filename)
        {
            if (String.IsNullOrEmpty(filename) || !File.Exists(filename))
            {
                return null;
            }

            SymbolFileInformation info;

            bool isPdb = (String.Compare(Path.GetExtension(filename), ".pdb", true) == 0);
            ulong baseAddress = (isPdb) ? 0x10000000UL : 0x00000000UL;
            uint fileLength = 0;

            if (isPdb)
            {
                fileLength = Convert.ToUInt32(new FileInfo(filename).Length);
            }

            if (!DbgHelp.SymInitialize(pHandle, String.Empty, false))
            {
                throw new Exception("Error initializing DbgHelp");
            }

            try
            {
                ulong result = DbgHelp.SymLoadModule64(pHandle, IntPtr.Zero, filename, null, baseAddress, fileLength);
                if (result == 0)
                {
                    throw new Exception("Error loading symbol module");
                }

                var module = new DbgHelp.IMAGEHLP_MODULE64
                                 {
                                     SizeOfStruct =
                                         (uint)
                                         System.Runtime.InteropServices.Marshal.SizeOf(
                                             typeof (DbgHelp.IMAGEHLP_MODULE64))
                                 };

                if (!DbgHelp.SymGetModuleInfo64(pHandle, result, ref module))
                {
                    throw new Exception("Error getting module info");
                }

                info = new SymbolFileInformation
                           {
                               UniqueIdentifier = GetIdentifierFromModule(module, isPdb).ToUpper(),
                               IsPdb = isPdb,
                               // This is Public if it doesn't contain line numbers or type info
                               Public = !(module.LineNumbers || module.TypeInfo)
                           };

                if (!DbgHelp.SymUnloadModule64(pHandle, result))
                {
                    throw new Exception("Error unloading module");
                }
            }
            finally
            {
                DbgHelp.SymCleanup(pHandle);
            }

            return info;
        }

        private static string GetIdentifierFromModule(DbgHelp.IMAGEHLP_MODULE64 module, bool isPdb)
        {
            if (isPdb)
            {
                return String.Format("{0}{1}", module.PdbSig70.ToString("N"), module.PdbAge);
            }
            return String.Format("{0:X}{1:X}", module.TimeDateStamp, module.ImageSize);
        }

        public static string GetCompressedFilePath(string path)
        {
            string result = path;

            if (!String.IsNullOrEmpty(path) && Path.HasExtension(path))
            {
                // Replace the path's final character with an underscore
                result = path.Substring(0, path.Length - 2) + "_";
            }

            return result;
        }

        private const char Delimiter = ',';

        public static List<string> ParseCSVLine(string line)
        {
            var fields = new List<string>();

            if (!String.IsNullOrEmpty(line))
            {
                int lastDelimiter = -1;
                bool inQuotedString = false;

                for (int i = 0; i < line.Length; i++)
                {
                    if ((line[i] == Delimiter) && (!inQuotedString))
                    {
                        fields.Add(line.Substring(lastDelimiter + 1, i - (lastDelimiter + 1)).Trim('"'));
                        lastDelimiter = i;
                    }
                    else if (
                        // The current character is a quote, and
                            (line[i] == '"') &&
                            (
                        // We are not yet in a quoted string, or
                                !inQuotedString ||
                        // The current character is the last one in the list, or
                                ((i + 1) == line.Length) ||
                        // The next character is a delimiter
                                (line[i + 1] == Delimiter)
                            )
                        )
                    {
                        // We are entering or exiting a quoted string,
                        // toggle our flag
                        inQuotedString = !inQuotedString;
                    }
                }

                // Add the final string
                if ((lastDelimiter + 1) < line.Length)
                {
                    fields.Add(line.Substring(lastDelimiter + 1, line.Length - (lastDelimiter + 1)));
                }
            }

            return fields;
        }

        private static readonly UTF8Encoding Utf8Encoding = new UTF8Encoding(false, true);

        /// <summary>
        /// Appends text to the specified file, inserting a newline character
        /// only if one is needed to cause the additional text to appear
        /// on the next line.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="contents">The text to add to the file.</param>
        public static void AppendLinesToFile(string path, string contents)
        {
            AppendLinesToFile(path, contents, Utf8Encoding);
        }

        /// <summary>
        /// Appends text to the specified file, inserting a newline character
        /// only if one is needed to cause the additional text to appear
        /// on the next line.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="contents">The text to add to the file.</param>
        /// <param name="encoding">The encoding to use.</param>
        public static void AppendLinesToFile(string path, string contents, Encoding encoding)
        {
            bool hasNewLine = true;

            if (File.Exists(path))
            {
                using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    if (fs.Length > 0)
                    {
                        // Read the last 2 bytes
                        var buff = new byte[2];
                        fs.Seek(fs.Length - 2, SeekOrigin.Begin);
                        if (fs.Read(buff, 0, 2) == 2)
                        {
                            // If the last to chars are 13 and 10, in that order,
                            // we have a new-line as the last entry in the file.
                            hasNewLine = ((buff[0] == 0xD) && (buff[1] == 0xA));
                        }
                    }
                }
            }

            using (var sw = new StreamWriter(path, true, encoding))
            {
                if (!hasNewLine)
                {
                    sw.Write(Environment.NewLine);
                }
                sw.Write(contents);
            }
        }
    }
}
